#ifndef VEC3_H_
#define VEC3_H_

#define _USE_MATH_DEFINES
#include <math.h>
#include "Vec2.h"

template <typename T>
class Vec3
{
public:

    /* Default Ctr. */
    Vec3() : m_x(0), m_y(0), m_z(0), m_pad(0)
    {}
    
    /* Overloaded Ctr. */
    Vec3(const T x, const T y, const T z) 
        : m_x(x), m_y(y), m_z(z), m_pad(0) {}

    /* Overloaded Ctr. */
    Vec3(const Vec3& v) 
        : m_x(v.GetX()), m_y(v.GetY()), m_z(v.GetZ()), m_pad(0) {}

    /* Const Get Functions. */
    T GetX() const
    {
        return m_x;
    }
    T GetY() const
    {
        return m_y;
    }
    T GetZ() const
    {
        return m_z;
    }
    /* Non Const Get Functions. */
    T& GetX()
    {
        return m_x;
    }
    T& GetY()
    {
        return m_y;
    }
    T& GetZ()
    {
        return m_z;
    }
    /* Set Functions. */
    Vec3& SetX ( const T x)
    {
        m_x = x;
        return *this;
    }
    Vec3& SetY ( const T y)
    {
        m_y = y;
        return *this;
    }
    Vec3& SetZ ( const T z)
    {
        m_z = z;
        return *this;
    }
    void SetStats(const T x, const T y, const T z)
    {
        m_x = x;
        m_y = y;
        m_z = z;
    }

    /* Conversion Functions. */
    Vec3<float> GetFVec()
    {
        return Vec3<float>((float)m_x, (float) m_y, (float) m_z);
    }

    /* Adds the given vector to this. */
    Vec3& operator+=(const Vec3& v)
    {
        m_x += v.GetX();
        m_y += v.GetY();
        m_z += v.GetZ();
        return *this;
    }

    /* Returns the value of the given vector added to this. */
    Vec3 operator+(const Vec3& v) const
    {
        return Vec3(m_x+v.GetX(), m_y+v.GetY(), m_z+v.GetZ());
    }

    /* Subtracts the given vector from this. */
    void operator-=(const Vec3& v)
    {
        m_x -= v.GetX();
        m_y -= v.GetY();
        m_z -= v.GetZ();
    }
    
    /* Returns the value of the given vector subtracted from this. */
    Vec3 operator-(const Vec3& v) const
    {
        return Vec3(m_x-v.GetX(), m_y-v.GetY(), m_z-v.GetZ());
    }

    /* Multiplies this vector by the given scalar. */
    void operator*=(const T value)
    {
        m_x *= value;
        m_y *= value;
        m_z *= value;
    }

    /* Returns a copy of this vector scaled the given value. */
    Vec3 operator*(const T value) const
    {
        return Vec3(m_x*value, m_y*value, m_z*value);
    }

    /* Index operators returning by value or reference. 
    depending on constness */
    T operator[](unsigned i) const
    {
        if (i == 0) return m_x;
        if (i == 1) return m_y;
        return m_z;
    }

    T& operator[](unsigned i) 
    {
        if (i == 0) return m_x;
        if (i == 1) return m_y;
        return m_z;
    }

    /**
     * Calculates and returns a component-wise product of this
     * vector with the given vector.
     */
    Vec3 componentProduct(const Vec3& v) const
    {
        return Vec3(m_x * v.GetX(), m_y * v.GetY(), m_z * v.GetZ());
    }

    /**
     * Performs a component-wise product with the given vector and
     * sets this vector to its result.
     */

    void componentProductUpdate(const Vec3& v)
    {
        m_x *= v.GetX();
        m_y *= v.GetY();
        m_z *= v.GetZ();
    }
    /**
     * Calculates and returns the vector product of this vector
     * with the given vector.
     */

    Vec3 CrossProduct(const Vec3& v) const
    {
        return Vec3(m_y * v.GetZ() - m_z * v.GetY(), 
                    m_z * v.GetX() - m_x * v.GetZ(), 
                    m_x * v.GetY() - m_y * v.GetX());
    }

    /**
     * Updates this vector to be the vector product of its current
     * value and the given vector.
     */
    void operator %=(const Vec3& v)
    {
        *this = CrossProduct(v);
    }

    /**
     * Calculates and returns the vector product of this vector
     * with the given vector.
     */

    Vec3 operator%(const Vec3& v) const
    {
        return CrossProduct(v);
    }

    /**
     * Calculates and returns the scalar product of this vector
     * with the given vector.
     */

    T DotProduct(const Vec3& v) const
    {
        return m_x * v.GetX() + m_y * v.GetY() + m_z * v.GetZ();
    }

    /**
     * Calculates and returns the scalar product of this vector
     * with the given vector.
     */

    T operator *(const Vec3& v) const
    {
        return m_x * v.GetX() + m_y* v.GetY() + m_z * v.GetZ();
    }        
    /**
     * Adds the given vector to this, scaled by the given amount.
     */
    void addScaledVector(const Vec3& v, T scale)
    {
        m_x += v.GetX() * scale;
        m_y += v.GetY() * scale;
        m_z += v.GetZ() * scale;
    }

    /** Gets the magnitude of this vector. */
    T magnitude() const
    {
        return sqrtf(squareMagnitude());
    }

    /** Gets the squared magnitude of this vector. */
    T squareMagnitude() const
    {
        return (m_x * m_x + 
                m_y * m_y + 
                m_z * m_z );
    }

    /** Turns a non-zero vector into a vector of unit length. */
    Vec3& normalise()
    {
        T length = magnitude();
        if (length > 0) 
        {
            (*this) *= ((T)1)/length;
        }
        return *this;
    }
    Vec3 getNormalisedVersion() const
    {
        T length = magnitude();
        Vec3<T> Return(*this);
        if(length > 0)
            Return = ( *this * ((T)1 / length));
        return Return;
    }
    /** Checks if the two vectors have identical components. */
    bool operator==(const Vec3& other) const
    {
        return m_x == other.GetX() &&
               m_y == other.GetY() &&
               m_z == other.GetZ();
    }

    /** Checks if the two vectors have non-identical components. */
    bool operator!=(const Vec3& other) const
    {
        return !(*this == other);
    }

    /**
     * Checks if this vector is component-by-component less than
     * the other. */
    bool operator<(const Vec3& other) const
    {
        return m_x < other.GetX() && 
               m_y < other.GetY() && 
               m_z < other.GetZ();
    }

    /* Checks if this vector is component-by-component greater than
     * the other. */
    bool operator>(const Vec3& other) const
    {
        return m_x > other.GetX() && m_y > other.GetY() && m_z > other.GetZ();
    }

    /*Checks if this vector is component-by-component less than
     * or equal to the other.*/
    bool operator<=(const Vec3& other) const
    {
        return m_x <= other.GetX() && m_y <= other.GetY() && m_z <= other.GetZ();
    }

    /*Checks if this vector is component-by-component greater than 
     * or equal to the other.*/
    bool operator>=(const Vec3& other) const
    {
        return m_x >= other.GetX() && m_y >= other.GetY() && m_z >= other.GetZ();
    }

    /** Zero all the components of the vector. */
    void clear() 
    { 
        m_x = m_y = m_z = 0;
    }

    /** Flips all the components of the vector. */
    Vec3& invert()
    {
        m_x = -m_x;
        m_y = -m_y;
        m_z = -m_z;
        return *this;
    }

    Vec3 GetInverted() const
    {
        return Vec3(-m_x, -m_y, -m_z);
    }

    /* Fills an array with x, y ,z */
    void FillArray(T* arr) const
    {
        *arr = m_x; arr++;
        *arr = m_y; arr++;
        *arr = m_z;
    }
private:
    T m_x;
    T m_y;
    T m_z;   
    T m_pad;
};

typedef Vec3<float> Vec3f;
typedef Vec3<int> Vec3i;

namespace Vector 
{
    template <typename T>
    static Vec3<T> CalculateNormalTri(const Vec3<T>& v1, const Vec3<T>& v2, const Vec3<T>& v3)
    {
        return Vec3<T>((v3 - v1) % (v1 - v2)).normalise();
    }
    template <typename T>
    static Vec3f lerp(const Vec3<T> v1, const Vec3<T> v2, float interval)
    { 
        if(interval > 1)
            interval = 1;
        return Vec3<T>(v1.GetX() + interval *(v2.GetX() - v1.GetX()),
            v1.GetY() + interval *(v2.GetY() - v1.GetY()),
            v1.GetZ() + interval *(v2.GetZ() - v1.GetZ()));
    }
    template <typename T>
    static Vec2f lerp(const Vec2<T> v1, const Vec2<T> v2, float interval)
    {
        if(interval > 1)
            interval = 1;
        return Vec2<T>(v1.GetX() + interval *(v2.GetX() - v1.GetX()),
            v1.GetY() + interval *(v2.GetY() - v1.GetY()));
    }
    static float lerp(const float f1, const float f2, float interval)
    {
        if(interval > 1)
            interval = 1;
        return (f1 + interval * (f2 - f1));
    }
}//namespace


#endif
